package com.wertop.viid.interceptor;

import cn.hutool.core.util.StrUtil;
import com.wertop.viid.common.annotation.RequireAuth;
import com.wertop.viid.common.constant.Constants;
import com.wertop.viid.common.utils.DigestUtils;
import com.wertop.viid.common.utils.LocalCacheUtil;
import com.wertop.viid.entity.DigestAuthInfo;
import com.wertop.viid.entity.UserEntity;
import com.wertop.viid.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description 权限验证
 * @Author yempty
 * @Date 2020/6/2 10:14
 */
@Component
@Slf4j
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    private static final String TEMPLATE_TWO = "{}:{}";
    private static final String TEMPLATE_THREE = "{}:{}:{}";
    private static final String TEMPLATE_FOUR = "{}:{}:{}:{}";
    private static final String DIGEST_TEMPLATE = "Digest realm=\"myRealm\",nonce=\"{}\",qop=\"auth\"";
    private static final String HEADER = "WWW-Authenticate";

    private static UserService userService;

    @Autowired
    private void setUserService(UserService userService) {
        AuthorizationInterceptor.userService = userService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        RequireAuth annotation;
        if (handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(RequireAuth.class);
        } else {
            return true;
        }

        if (annotation == null) {
            return true;
        }

        // 没有 Authorization 请求头，或者 Authorization 认证信息验证不通过，拦截
        if (!isAuth(request, response)) {
            return false;
        }

        return true;
    }

    private boolean isAuth(HttpServletRequest request, HttpServletResponse response) {
        String authStr = request.getHeader(Constants.AUTHORIZATION);
        log.info("请求 Authorization 的内容：" + authStr);
        if (authStr == null || authStr.length() <= DigestUtils.HEAD.length()) {
            // 没有 Authorization 请求头，开启质询
            return challenge(response);
        }

        DigestAuthInfo authObject = DigestUtils.getAuthInfoObject(authStr);
        // 解析 Digest 异常
        if (authObject == null) {
            return challenge(response);
        }
        /*
         * 生成 response 的算法：
         *  response = MD5(MD5(username:realm:password):nonce:nc:cnonce:qop:MD5(<request-method>:url))
         */
        // 根据用户名查询存储的密码
        UserEntity userEntity = userService.queryByName(authObject.getUsername());
        // 用户名不存在，开启质询
        if (userEntity == null) {
            return challenge(response);
        }
        String HA1 = DigestUtils.md5(StrUtil.format(TEMPLATE_THREE, authObject.getUsername(), authObject.getRealm(), userEntity.getPassword()));
        String HD = StrUtil.format(TEMPLATE_FOUR, authObject.getNonce(), authObject.getNc(), authObject.getCnonce(), authObject.getQop());
        String HA2 = DigestUtils.md5(StrUtil.format(TEMPLATE_TWO, request.getMethod(), authObject.getUri()));
        String responseValid = DigestUtils.md5(StrUtil.format(TEMPLATE_THREE, HA1, HD, HA2));

        // 如果 Authorization 中的 response（浏览器生成的） 与期望的 response（服务器计算的） 相同，则验证通过
        log.info("Authorization 中的 response: " + authObject.getResponse());
        log.info("期望的 response: " + responseValid);
        if (responseValid.equals(authObject.getResponse())) {
            /* 判断 nc 的值，用来防重放攻击 */
            // 判断此次请求的 Authorization 请求头里面的 nc 值是否大于之前保存的 nc 值
            // 大于，替换旧值，然后 return true
            // 否则，return false

            // 第一次放过
            if (Constants.FIRST_NONCE.equals(authObject.getNc())) {
                log.info("第一次访问直接通过，增加至缓存");
                return true;
            } else {
                // 未过期，触发保活机制，重置有效期
                String userIdentify = request.getHeader(Constants.USER_IDENTIFY);
                if (StringUtils.isNotBlank(userIdentify)) {
                    String deviceId = LocalCacheUtil.get(userIdentify);
                    if (StringUtils.isNotBlank(deviceId)) {
                        return true;
                    } else {
                        log.info("缓存中未获取到，已过期，需重新注册");
                    }
                } else {
                    log.info("请求头未获取到User-Identify，需重新注册");
                }
            }
        }
        // 验证不通过，重复质询
        return challenge(response);
    }

    /**
     * 质询：返回状态码 401 和 WWW-Authenticate 响应头
     * 质询前，重置或删除保存的与该用户关联的 nc 值（nc：nonce计数器，是一个16进制的数值，表示同一nonce下客户端发送出请求的数量）
     * 将 nc 置为初始值 0， 这里代码省略
     *
     * @param response 响应
     * @return 返回false，则表示拦截器拦截请求
     */
    private boolean challenge(HttpServletResponse response) {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        String header = StrUtil.format(DIGEST_TEMPLATE, DigestUtils.generateToken());
        response.addHeader(HEADER, header);
        return false;
    }

}
